#!/usr/bin/python3
# convert the keversoft xml file into a json format

import xml.etree.ElementTree as ET
import sys
import json
import re
from optparse import OptionParser

parser = OptionParser(usage="usage: %prog [options] filename")
parser.add_option("-f", "--file", dest="filename")
parser.add_option("-d", "--dictionary", dest="dic", action="store_true", default=False)
parser.add_option("-m",type="string",dest="modifier_file")
(options,args) = parser.parse_args()

def get_field_text(el,s):
    tmp = el.find(s)
    if(tmp is not None):
        return tmp.text
    else:
        return None

def field_to_dict(field):
    name = get_field_text(field,'Name')
    fdesc = get_field_text(field,'Description')
    typ = get_field_text(field,'Type')
    length = int(get_field_text(field,'BitLength'))
    res = get_field_text(field,'Resolution')
    units = get_field_text(field,'Units')
    signed = True if get_field_text(field,'Signed') == 'true' \
        else False
    offset = int(get_field_text(field,'BitOffset'))

    try:
        if(res is not None):
            res = float(res)
    except(ValueError):
        m = re.match(r'([\.\d]+)\s+(\w+)',res)
        res = float(m.group(1))
        units = m.group(2)
        
    if(typ == 'Lookup table'):
        units = {int(enum.get('Value')):enum.get('Name') \
                     for enum in field.find('EnumValues')}
    elif(typ == 'Latitude'):
        res = 1e-7
        units = 'N'
        signed = True
    elif(typ == "Longitude"):
        res = 1e-7
        units = 'E'
        signed = True
                
    return({'name':name, 'field_description':fdesc, 
            'bitlength':length, 'resolution':res, 
            'units':units, "signed":signed, 'offset':offset})


if(options.filename):
    fname = filename
elif(len(args) == 1):
    fname = args[0]
else:
    parser.print_help()
    sys.exit(1)

t = ET.parse(fname)

d = {} if options.dic else []
pgns = t.find('PGNs')
for pgninfo in pgns:
    pgn = int(get_field_text(pgninfo,'PGN'))
#    print(pgn)
    desc = get_field_text(pgninfo,'Description')
#    fields = {} if options.dic else []
    fields = []
    try:
        repeating_fields = int(get_field_text(pgninfo,'RepeatingFields'))
#        field_str_list = []
        fieldsread = pgninfo.find('Fields')
        fieldlen = len(fieldsread)
        repeatingfieldstart = fieldlen-repeating_fields
        if (repeatingfieldstart < fieldlen):
            repeatingindex = repeatingfieldstart - 1
        else:
            repeatingindex = -1
        pos = 0
        for i,field in enumerate(fieldsread):
            d_field = field_to_dict(field)
            
                    # check for missing fields
            if (pos < d_field['offset']):
                length = d_field['offset']-pos
                sys.stderr.write('missing field')
            pos += d_field['bitlength']
            
            if (i == repeatingfieldstart):
                fields.append([])

            if(options.dic):
                if (len(fields) > 0) and isinstance(fields[-1],list):
                    fields[-1].append(d_field)
                else:
                    fields.append(d_field)
            else: 
                fields.append('not currently supported')
                #[d_field['name'], d_field['fdesc'], d_field['length'], d_fields['res'], units, signed])
    except(TypeError):
       # sys.stderr.write("couldn't get fields for " + pgn)
        fields = None

#    field_str = assem_field_str(field_str_list)
    if(options.dic):
        d[str(pgn)] = {"description": desc, "fields":fields,
                       "repeating_fields":repeating_fields}#, "field_str":field_str}
    else:
        d.append((pgn, desc, fields))#, field_str))


def recur_update(d,mod_d):
    for k,v in mod_d.items():
        if (isinstance(v,dict)):
            recur_update(d.setdefault(k),v)
        elif (isinstance(v,list)):
            for i,j in zip(d[k],v):
                recur_update(i,j)
        else:
            d[k] = v

if(options.dic and options.modifier_file):
    mod_d = json.load(open(options.modifier_file))
    recur_update(d,mod_d)
        

print(json.dumps(d,indent=4))
    

